#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
多相机录制程序 - 改进版
"""

import threading
import time
import os
import json
import serial
import sys
import logging
from typing import List, Dict, Any, Optional

import mvsdk


class ConfigurationError(Exception):
    """配置错误异常"""
    pass


class CameraError(Exception):
    """相机错误异常"""
    pass


class MarkerError(Exception):
    """打标器错误异常"""
    pass


class ConfigManager:
    """配置管理器"""
    
    def __init__(self, config_file: str = 'config.json'):
        self.config_file = config_file
        self._config = self._load_config()
        self._validate_config()
    
    def _load_config(self) -> Dict[str, Any]:
        """加载配置文件"""
        if not os.path.exists(self.config_file):
            raise ConfigurationError(f"配置文件 {self.config_file} 不存在")
        
        try:
            with open(self.config_file, 'r', encoding='utf-8') as f:
                config = json.load(f)
            logging.info(f"配置文件加载成功: {self.config_file}")
            return config
        except json.JSONDecodeError as e:
            raise ConfigurationError(f"配置文件格式错误: {e}")
        except Exception as e:
            raise ConfigurationError(f"读取配置文件失败: {e}")
    
    def _validate_config(self) -> None:
        """验证配置文件完整性"""
        required_sections = ['camera', 'experiment']
        for section in required_sections:
            if section not in self._config:
                raise ConfigurationError(f"配置文件缺少必需的 '{section}' 部分")
        
        camera_config = self._config['camera']
        required_camera_params = [
            'exposure_time', 'rgbgain', 'video_quality', 
            'cam_gamma', 'cam_Contrast', 'cam_Saturation', 
            'cam_Sharpness', 'cam_AnalogGain', 'save_dir'
        ]
        
        for param in required_camera_params:
            if param not in camera_config:
                raise ConfigurationError(f"相机配置缺少必需参数: {param}")
        
        experiment_config = self._config['experiment']
        if 'com_port' not in experiment_config:
            raise ConfigurationError("实验配置缺少必需参数: com_port")
    
    @property
    def camera_config(self) -> Dict[str, Any]:
        """获取相机配置"""
        return self._config['camera']
    
    @property
    def experiment_config(self) -> Dict[str, Any]:
        """获取实验配置"""
        return self._config['experiment']
    
    @property
    def save_directory(self) -> str:
        """获取保存目录路径"""
        save_dir = os.path.join(
            os.getcwd(), 
            self.camera_config['save_dir'], 
            "video_recordings"
        )
        os.makedirs(save_dir, exist_ok=True)
        return save_dir


class SerialMarker:
    """串口打标器类"""
    
    def __init__(self, port: str, baudrate: int = 115200, timeout: float = 1.0):
        if not port:
            raise MarkerError("串口端口不能为空")
        
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self._serial_connection = None
        self._connect()
    
    def _connect(self) -> None:
        """连接串口"""
        try:
            self._serial_connection = serial.Serial(
                self.port, 
                self.baudrate, 
                timeout=self.timeout
            )
            logging.info(f"串口 {self.port} 连接成功")
        except serial.SerialException as e:
            raise MarkerError(f"无法连接串口 {self.port}: {e}")
    
    def send_marker(self, marker_value: int) -> None:
        """发送标记值"""
        if not self._serial_connection or not self._serial_connection.is_open:
            raise MarkerError("串口未连接")
        
        try:
            bytes_value = marker_value.to_bytes(1, byteorder='big')
            self._serial_connection.write(bytes_value)
        except Exception as e:
            logging.error(f"发送标记失败: {e}")
            raise MarkerError(f"发送标记失败: {e}")
    
    def close(self) -> None:
        """关闭串口连接"""
        if self._serial_connection and self._serial_connection.is_open:
            self._serial_connection.close()
            logging.info("串口连接已关闭")


class CameraRecorder(threading.Thread):
    """相机录制线程类"""
    
    def __init__(self, camera_index: int, config: ConfigManager, 
                 marker: SerialMarker, exit_event: threading.Event, 
                 start_barrier: threading.Barrier):
        super().__init__(name=f"Camera-{camera_index}")
        self.camera_index = camera_index
        self.config = config
        self.marker = marker
        self.exit_event = exit_event
        self.start_barrier = start_barrier
        
        self._camera_handle = 0
        self._frame_buffer = 0
        self._recording_initialized = False
        
        self._initialize_camera()
    
    def _initialize_camera(self) -> None:
        """初始化相机"""
        device_list = mvsdk.CameraEnumerateDevice()
        device_count = len(device_list)
        
        if device_count == 0:
            raise CameraError("未找到任何相机设备")
        
        if self.camera_index >= device_count:
            raise CameraError(
                f"相机索引 {self.camera_index} 无效，"
                f"只有 {device_count} 个相机设备"
            )
        
        device_info = device_list[self.camera_index]
        logging.info(
            f"使用相机 {self.camera_index}: "
            f"{device_info.GetFriendlyName()} {device_info.GetPortType()}"
        )
        
        try:
            self._camera_handle = mvsdk.CameraInit(device_info, -1, -1)
            self._configure_camera()
            self._initialize_recording()
            
        except mvsdk.CameraException as e:
            raise CameraError(f"相机初始化失败: {e.message}")
    
    def _configure_camera(self) -> None:
        """配置相机参数"""
        camera_config = self.config.camera_config
        
        mvsdk.CameraSetTriggerMode(self._camera_handle, 0)
        
        capability = mvsdk.CameraGetCapability(self._camera_handle)
        is_mono_camera = (capability.sIspCapacity.bMonoSensor != 0)
        
        if is_mono_camera:
            mvsdk.CameraSetIspOutFormat(
                self._camera_handle, 
                mvsdk.CAMERA_MEDIA_TYPE_MONO8
            )
        
        mvsdk.CameraSetAeState(self._camera_handle, 0)
        mvsdk.CameraSetExposureTime(
            self._camera_handle, 
            int(camera_config['exposure_time']) * 1000
        )
        
        rgb_gains = camera_config['rgbgain']
        mvsdk.CameraSetGain(
            self._camera_handle, 
            int(rgb_gains[0]), 
            int(rgb_gains[1]), 
            int(rgb_gains[2])
        )
        
        mvsdk.CameraSetGamma(self._camera_handle, int(camera_config['cam_gamma']))
        mvsdk.CameraSetContrast(self._camera_handle, camera_config['cam_Contrast'])
        mvsdk.CameraSetSaturation(self._camera_handle, camera_config['cam_Saturation'])
        mvsdk.CameraSetSharpness(self._camera_handle, camera_config['cam_Sharpness'])
        mvsdk.CameraSetAnalogGain(self._camera_handle, camera_config['cam_AnalogGain'])
        mvsdk.CameraSetRotate(self._camera_handle, 2)
        
        mvsdk.CameraPlay(self._camera_handle)
        
        frame_buffer_size = (
            capability.sResolutionRange.iWidthMax * 
            capability.sResolutionRange.iHeightMax * 
            (1 if is_mono_camera else 3)
        )
        self._frame_buffer = mvsdk.CameraAlignMalloc(frame_buffer_size, 16)
        
        logging.info(f"相机 {self.camera_index} 配置完成")
    
    def _initialize_recording(self) -> None:
        """初始化录制"""
        timestamp = int(time.time())
        video_quality = self.config.camera_config['video_quality']
        filename = f"{timestamp}_video{self.camera_index}_q{video_quality}-h264.mp4"
        filepath = os.path.join(self.config.save_directory, filename)
        
        error_code = mvsdk.CameraInitRecord(
            self._camera_handle, 
            4, 
            filepath, 
            True, 
            video_quality, 
            25
        )
        
        if error_code != 0:
            raise CameraError(f"录制初始化失败，错误代码: {error_code}")
        
        self._recording_initialized = True
        logging.info(f"相机 {self.camera_index} 录制初始化完成: {filepath}")
    
    
    def run(self) -> None:
        """运行录制线程"""
        if not self._recording_initialized:
            logging.error(f"相机 {self.camera_index} 录制未正确初始化")
            self._cleanup()
            return
        
        try:
            # 等待所有相机准备就绪，然后同步开始
            logging.info(f"相机 {self.camera_index} 等待同步开始...")
            self.start_barrier.wait()
            logging.info(f"相机 {self.camera_index} 开始录制")
            
            while not self.exit_event.is_set():
                try:
                    raw_data, frame_header = mvsdk.CameraGetImageBuffer(
                        self._camera_handle, 200
                    )
                    if raw_data:
                        mvsdk.CameraImageProcess(
                            self._camera_handle, 
                            raw_data, 
                            self._frame_buffer, 
                            frame_header
                        )
                        mvsdk.CameraReleaseImageBuffer(self._camera_handle, raw_data)
                        mvsdk.CameraPushFrame(
                            self._camera_handle, 
                            self._frame_buffer, 
                            frame_header
                        )
                        
                except mvsdk.CameraException as e:
                    if e.error_code != mvsdk.CAMERA_STATUS_TIME_OUT:
                        logging.error(f"获取图像缓冲区失败: {e.message}")
                
        except Exception as e:
            logging.error(f"相机 {self.camera_index} 录制过程中出错: {e}")
        finally:
            self._cleanup()
    
    def _cleanup(self) -> None:
        """清理资源"""
        if self._camera_handle > 0:
            mvsdk.CameraStopRecord(self._camera_handle)
            mvsdk.CameraUnInit(self._camera_handle)
            self._camera_handle = 0
            
        if self._frame_buffer > 0:
            mvsdk.CameraAlignFree(self._frame_buffer)
            self._frame_buffer = 0
            
        logging.info(f"相机 {self.camera_index} 资源清理完成")


class MultiCameraRecorder:
    """多相机录制管理器"""
    
    def __init__(self, config_file: str = 'config.json'):
        self.config = ConfigManager(config_file)
        self.marker = None
        self.camera_threads: List[CameraRecorder] = []
        self.exit_event = threading.Event()
        self.start_barrier = None
        
        self._setup_logging()
        self._initialize_marker()
        self._initialize_cameras()
    
    def _setup_logging(self) -> None:
        """设置日志"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.StreamHandler(sys.stdout),
                logging.FileHandler('camera_recording.log', encoding='utf-8')
            ]
        )
    
    def _initialize_marker(self) -> None:
        """初始化打标器"""
        com_port = self.config.experiment_config['com_port']
        try:
            self.marker = SerialMarker(com_port)
        except MarkerError as e:
            logging.error(f"打标器初始化失败: {e}")
            raise
    
    def _initialize_cameras(self) -> None:
        """初始化所有相机"""
        device_list = mvsdk.CameraEnumerateDevice()
        device_count = len(device_list)
        
        if device_count == 0:
            raise CameraError("未找到任何相机设备")
        
        # 创建同步屏障，等待所有相机准备就绪
        self.start_barrier = threading.Barrier(device_count)
        
        logging.info("发现的相机设备:")
        for i, device_info in enumerate(device_list):
            logging.info(f"  {i}: {device_info.GetFriendlyName()}")
        
        for camera_index in range(device_count):
            try:
                camera_thread = CameraRecorder(
                    camera_index, 
                    self.config, 
                    self.marker, 
                    self.exit_event,
                    self.start_barrier
                )
                self.camera_threads.append(camera_thread)
            except CameraError as e:
                logging.error(f"相机 {camera_index} 初始化失败: {e}")
                raise
    
    def start_recording(self) -> None:
        """开始录制"""
        if not self.camera_threads:
            raise CameraError("没有可用的相机线程")
        
        logging.info("开始启动所有相机录制线程...")
        for thread in self.camera_threads:
            thread.start()
        
        # 立即发送统一的打标信号
        self._send_synchronized_markers()
        
        logging.info("所有相机录制已启动")
    
    def stop_recording(self) -> None:
        """停止录制"""
        logging.info("准备停止录制...")
        self.exit_event.set()
        
        for thread in self.camera_threads:
            if thread.is_alive():
                thread.join()
        
        logging.info("所有相机录制已停止")
    
    def run_interactive(self) -> None:
        """运行交互式录制"""
        try:
            self.start_recording()
            
            print("录制已开始...")
            print("按 'q' + Enter 键退出程序")
            
            while True:
                try:
                    user_input = input().strip().lower()
                    if user_input == 'q':
                        break
                except (EOFError, KeyboardInterrupt):
                    break
            
        except Exception as e:
            logging.error(f"录制过程中出错: {e}")
            raise
        finally:
            self.stop_recording()
            if self.marker:
                self.marker.close()
    
    def _send_synchronized_markers(self) -> None:
        """发送同步打标信号"""
        try:
            # 为每个相机发送开始标记
            for camera_index in range(len(self.camera_threads)):
                start_marker = camera_index + 1
                logging.info(f"发送相机 {camera_index} 开始标记: {start_marker}")
                
                # 持续发送标记1秒
                start_time = time.time()
                while time.time() - start_time < 1.0:
                    self.marker.send_marker(start_marker)
                    time.sleep(0.01)
            
            # 发送停止标记
            self.marker.send_marker(0)
            logging.info("所有相机打标信号发送完成")
            
        except MarkerError as e:
            logging.error(f"发送打标信号失败: {e}")


def main():
    """主函数"""
    try:
        recorder = MultiCameraRecorder()
        recorder.run_interactive()
        
    except (ConfigurationError, CameraError, MarkerError) as e:
        logging.error(f"程序启动失败: {e}")
        sys.exit(1)
    except Exception as e:
        logging.error(f"未知错误: {e}")
        sys.exit(1)
    
    logging.info("程序正常退出")


if __name__ == '__main__':
    main()